﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Slb.Ocean.Core;
using Slb.Ocean.Petrel.IO.Attributes;
using System.Globalization;
using System.Xml;
using Public.Ocean.Petrel.Extensions.Internal;
using System.Linq.Expressions;
using System.ComponentModel;

namespace Public.Ocean.Petrel.Extensions.Attributes
{
    public abstract class CustomAttributesBase
    {
        static StringValueConverter converter = new StringValueConverter();

        Droid droid;
        IDataSourceManager manager;
        ICustomAttributeService service;
        CustomAttributeEventService eventService;

        internal Droid GetDroid() { return droid; }

        static object cacheNotResolved = new object();
        object dataCache = cacheNotResolved;
        public TData GetData<TData>() where TData : class
        {
            if (dataCache == cacheNotResolved)
            {
                dataCache = manager.Resolve(droid);
            }
            return dataCache as TData;
        }

        public static void SetCachedData(CustomAttributesBase attributes, IIdentifiable owner)
        {
            if (attributes == null) return;
            if (owner == null) return;

            if (attributes.dataCache == cacheNotResolved)
            {
                attributes.dataCache = owner;
            }
        }

        protected CustomAttributesBase()
        {
        }

        protected CustomAttributesBase(Droid droid, IDataSourceManager manager, ICustomAttributeService service, CustomAttributeEventService eventService)
        {
            Initialize(this, droid, manager, service, eventService);
        }

        public static void Initialize(CustomAttributesBase attributes, Droid droid, IDataSourceManager manager, ICustomAttributeService service, CustomAttributeEventService eventService)
        {
            if (attributes == null) return;

            if (attributes.droid != null) throw new InvalidOperationException("Cannot initialize more than once.");

            attributes.droid = droid;
            attributes.manager = manager;
            attributes.service = service;
            attributes.eventService = eventService;
        }

        public static string NamespaceFor<TAttributes>()
        {
            return NamespaceFor(typeof(TAttributes));
        }

        public static bool HasAttributes<TAttributes>(Droid droid, IDataSourceManager manager, ICustomAttributeService service)
        {
            string ns = NamespaceFor<TAttributes>();
            var attributes = service.GetAttributes(droid, manager);
            if (attributes == null) { 
                return false;
            } else {
                return attributes.Any(e => e.Key.AttributeNamespace == ns);
            }
        }

        private static string NamespaceFor(Type type)
        {
            return type.Name;
        }

        public static void Mock(CustomAttributesBase attributes)
        {
            attributes.droid = Droid.Empty;
            attributes.dataCache = new object();
            attributes.manager = null;
            attributes.service = new MockCustomAttributeService();
            attributes.eventService = CustomAttributeEventService.Instance;
        }

        protected Dictionary<string, string> ToDictionary(string attributeNamespace)
        {
            Dictionary<string, string> result = new Dictionary<string, string>();
            foreach (var entry in service.GetAttributes(droid, manager))
            {
                if (entry.Key.AttributeNamespace == attributeNamespace)
                {
                    result.Add(entry.Key.AttributeName, entry.Value);
                }
            }
            return result;
        }

        protected void SetValue(string ns, string key, string value)
        {
            var namespacedKey = new CustomAttributeKey(ns, key);
            if (service.HasAttribute(droid, manager, namespacedKey))
            {
                if (value != null)
                {
                    service.SetAttribute(droid, manager, namespacedKey, value);
                }
                else
                {
                    service.RemoveAttribute(droid, manager, new CustomAttributeKey(ns, key));
                }
            }
            else
            {
                if (value != null)
                {
                    service.AddAttribute(droid, manager, namespacedKey, value);
                }
            }
            eventService.Raise(this, key);
        }

        protected void SetTypedValue<TValue>(string ns, string key, TValue value) { 
            SetValue(ns, key, converter.Pack<TValue>(value)); 
        }

        protected string GetValue(string ns, string key)
        {
            var namespacedKey = new CustomAttributeKey(ns, key);
            if (service.HasAttribute(droid, manager, namespacedKey))
            {
                return service.GetAttribute(droid, manager, new CustomAttributeKey(ns, key));
            }
            else
            {
                return null;
            }
        }

        protected TValue GetTypedValue<TValue>(string ns, string key)
        {
            return converter.Unpack<TValue>(GetValue(ns, key));
        }

        protected TValue GetTypedValue<TValue>(string ns, string key, TValue valueIfMissing)
        {
            return converter.Unpack<TValue>(GetValue(ns, key), valueIfMissing);
        }

        protected TValue GetForProperty<TParent, TValue>(TParent owner, Expression<Func<TParent, TValue>> expression)
        {
            var memberExpression = (MemberExpression)expression.Body;
            return GetTypedValue<TValue>(GetType().Name, memberExpression.Member.Name);
        }

        protected TValue GetForProperty<TParent, TValue>(TParent owner, Expression<Func<TParent, TValue>> expression, TValue valueIfMissing)
        {
            var memberExpression = (MemberExpression)expression.Body;
            return GetTypedValue<TValue>(GetType().Name, memberExpression.Member.Name, valueIfMissing);
        }

        protected void SetForProperty<TParent, TValue>(TParent owner, Expression<Func<TParent, TValue>> expression, TValue value)
        {
            var memberExpression = (MemberExpression)expression.Body;
            SetTypedValue<TValue>(GetType().Name, memberExpression.Member.Name, value);
        }

        protected void AttachEvent(PropertyChangedEventHandler handler)
        {
            eventService.Attach(this, GetType().Name, handler);
        }

        protected void DetachEvent(PropertyChangedEventHandler handler)
        {
            eventService.Detach(this, GetType().Name, handler);
        }
    }
}